# 应用层roam过程分析 在应用层使用下面的命令进行roam的时候,应用层干了什么事情?本文重点分析应用层wpa程序roam过程。 ```bash wpa_cli -p/var/run/wpa_supplicant-ath0 -iath0 roam 00:0f:ff:01:40:12 ``` ## wpa_supplicant 漫游过程分析 当我们主动发送roam命令的时候,wpa_s收到wpa_cli命令时候进入`wpa_supplicant_ctrl_iface_process`函数 ```c char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, char *buf, size_t *resp_len) { if (os_strncmp(buf, "ROAM ", 5) == 0) { if (wpa_supplicant_ctrl_iface_roam(wpa_s, buf + 5)) reply_len = -1; } } ``` 接着调用`wpa_supplicant_ctrl_iface_roam`函数 ### wpa_supplicant_ctrl_iface_roam ```c static int wpa_supplicant_ctrl_iface_roam(struct wpa_supplicant *wpa_s, char *addr) { u8 bssid[ETH_ALEN]; struct wpa_bss *bss; struct wpa_ssid *ssid = wpa_s->current_ssid; // 漫游目标addr转换为bssid if (hwaddr_aton(addr, bssid)) { wpa_printf(MSG_DEBUG, "CTRL_IFACE ROAM: invalid " "address '%s'", addr); return -1; } // 如果当前连接为空,则直接返回,不允许roam if (!ssid) { wpa_printf(MSG_DEBUG, "CTRL_IFACE ROAM: No network " "configuration known for the target AP"); return -1; } //根据传入的bssid 得到bss bss = wpa_bss_get(wpa_s, bssid, ssid->ssid, ssid->ssid_len); /* * TODO: Find best network configuration block from configuration to * allow roaming to other networks */ wpa_s->reassociate = 1; wpa_supplicant_connect(wpa_s, bss, ssid);// 继续往下分析 return 0; } ``` 接下来分析`wpa_supplicant_connect`函数 #### wpa_supplicant_connect ```c int wpa_supplicant_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *selected, struct wpa_ssid *ssid) { // 一段调试信息 wpa_msg(wpa_s, MSG_DEBUG, "Considering connect request: reassociate: %d selected: " MACSTR " bssid: " MACSTR " pending: " MACSTR " wpa_state: %s ssid=%p current_ssid=%p", wpa_s->reassociate, MAC2STR(selected->bssid), MAC2STR(wpa_s->bssid), MAC2STR(wpa_s->pending_bssid), wpa_supplicant_state_txt(wpa_s->wpa_state), ssid, wpa_s->current_ssid); /* * Do not trigger new association unless the BSSID has changed or if * reassociation is requested. If we are in process of associating with * the selected BSSID, do not trigger new attempt. */ // 除非BSSID已更改或请求重新关联,否则不要触发新的关联。 // 如果我们正在与所选BSSID关联,请不要触发新的尝试。 if (wpa_s->reassociate || (os_memcmp(selected->bssid, wpa_s->bssid, ETH_ALEN) != 0 && ((wpa_s->wpa_state != WPA_ASSOCIATING && wpa_s->wpa_state != WPA_AUTHENTICATING) || (!is_zero_ether_addr(wpa_s->pending_bssid) && os_memcmp(selected->bssid, wpa_s->pending_bssid, ETH_ALEN) != 0) || (is_zero_ether_addr(wpa_s->pending_bssid) && ssid != wpa_s->current_ssid)))) { if (wpa_supplicant_scard_init(wpa_s, ssid)) { wpa_supplicant_req_new_scan(wpa_s, 10, 0); return 0; } wpa_msg(wpa_s, MSG_DEBUG, "Request association with " MACSTR, MAC2STR(selected->bssid)); // 重点 开始关联 wpa_supplicant_associate(wpa_s, selected, ssid); } else { wpa_dbg(wpa_s, MSG_DEBUG, "Already associated or trying to " "connect with the selected AP"); } return 0; } ``` 接下来分析`wpa_supplicant_associate` ##### wpa_supplicant_associate ```c void wpa_supplicant_associate(struct wpa_supplicant *wpa_s, struct wpa_bss *bss, struct wpa_ssid *ssid) { struct wpa_connect_work *cwork; int rand_style; wpa_s->own_disconnect_req = 0; wpa_s->own_reconnect_req = 0; /* * If we are starting a new connection, any previously pending EAPOL * RX cannot be valid anymore. */ wpabuf_free(wpa_s->pending_eapol_rx); wpa_s->pending_eapol_rx = NULL; if (ssid->mac_addr == -1) rand_style = wpa_s->conf->mac_addr; else rand_style = ssid->mac_addr; wpa_s->multi_ap_ie = 0; wmm_ac_clear_saved_tspecs(wpa_s); wpa_s->reassoc_same_bss = 0; wpa_s->reassoc_same_ess = 0; #ifdef CONFIG_TESTING_OPTIONS wpa_s->testing_resend_assoc = 0; #endif /* CONFIG_TESTING_OPTIONS */ /* * Get wideband support for the interface. * It is disabled by default unless explicitly enabled. */ wpa_s->wideband_support = 0; wpa_drv_get_wideband_support(wpa_s); if (wpa_s->last_ssid == ssid) { wpa_dbg(wpa_s, MSG_DEBUG, "Re-association to the same ESS"); wpa_s->reassoc_same_ess = 1; if (wpa_s->current_bss && wpa_s->current_bss == bss) { wmm_ac_save_tspecs(wpa_s); wpa_s->reassoc_same_bss = 1; } else if (wpa_s->current_bss && wpa_s->current_bss != bss) { os_get_reltime(&wpa_s->roam_start); } } else { #ifdef CONFIG_SAE wpa_s_clear_sae_rejected(wpa_s); wpa_s_setup_sae_pt(wpa_s->conf, ssid); #endif /* CONFIG_SAE */ } if (rand_style > 0 && !wpa_s->reassoc_same_ess) { if (wpas_update_random_addr(wpa_s, rand_style) < 0) return; wpa_sm_pmksa_cache_flush(wpa_s->wpa, ssid); } else if (rand_style == 0 && wpa_s->mac_addr_changed) { if (wpa_drv_set_mac_addr(wpa_s, NULL) < 0) { wpa_msg(wpa_s, MSG_INFO, "Could not restore permanent MAC address"); return; } wpa_s->mac_addr_changed = 0; if (wpa_supplicant_update_mac_addr(wpa_s) < 0) { wpa_msg(wpa_s, MSG_INFO, "Could not update MAC address information"); return; } wpa_msg(wpa_s, MSG_DEBUG, "Using permanent MAC address"); } wpa_s->last_ssid = ssid; #ifdef CONFIG_IBSS_RSN ibss_rsn_deinit(wpa_s->ibss_rsn); wpa_s->ibss_rsn = NULL; #else /* CONFIG_IBSS_RSN */ if (ssid->mode == WPAS_MODE_IBSS && !(ssid->key_mgmt & (WPA_KEY_MGMT_NONE | WPA_KEY_MGMT_WPA_NONE))) { wpa_msg(wpa_s, MSG_INFO, "IBSS RSN not supported in the build"); return; } #endif /* CONFIG_IBSS_RSN */ if (ssid->mode == WPAS_MODE_AP || ssid->mode == WPAS_MODE_P2P_GO || ssid->mode == WPAS_MODE_P2P_GROUP_FORMATION) { #ifdef CONFIG_AP if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_AP)) { wpa_msg(wpa_s, MSG_INFO, "Driver does not support AP " "mode"); return; } if (wpa_supplicant_create_ap(wpa_s, ssid) < 0) { wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED); if (ssid->mode == WPAS_MODE_P2P_GROUP_FORMATION) wpas_p2p_ap_setup_failed(wpa_s); return; } wpa_s->current_bss = bss; #else /* CONFIG_AP */ wpa_msg(wpa_s, MSG_ERROR, "AP mode support not included in " "the build"); #endif /* CONFIG_AP */ return; } if (ssid->mode == WPAS_MODE_MESH) { #ifdef CONFIG_MESH if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_MESH)) { wpa_msg(wpa_s, MSG_INFO, "Driver does not support mesh mode"); return; } if (bss) ssid->frequency = bss->freq; if (wpa_supplicant_join_mesh(wpa_s, ssid) < 0) { wpa_msg(wpa_s, MSG_ERROR, "Could not join mesh"); return; } wpa_s->current_bss = bss; wpa_msg(wpa_s, MSG_INFO, MESH_GROUP_STARTED "ssid=\"%s\" id=%d", wpa_ssid_txt(ssid->ssid, ssid->ssid_len), ssid->id); wpas_notify_mesh_group_started(wpa_s, ssid); #else /* CONFIG_MESH */ wpa_msg(wpa_s, MSG_ERROR, "mesh mode support not included in the build"); #endif /* CONFIG_MESH */ return; } /* * Set WPA state machine configuration to match the selected network now * so that the information is available before wpas_start_assoc_cb() * gets called. This is needed at least for RSN pre-authentication where * candidate APs are added to a list based on scan result processing * before completion of the first association. */ wpa_supplicant_rsn_supp_set_config(wpa_s, ssid); #ifdef CONFIG_DPP if (wpas_dpp_check_connect(wpa_s, ssid, bss) != 0) return; #endif /* CONFIG_DPP */ #ifdef CONFIG_TDLS if (bss) wpa_tdls_ap_ies(wpa_s->wpa, (const u8 *) (bss + 1), bss->ie_len); #endif /* CONFIG_TDLS */ #ifdef CONFIG_MBO wpas_mbo_check_pmf(wpa_s, bss, ssid); #endif /* CONFIG_MBO */ if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) && ssid->mode == WPAS_MODE_INFRA) { sme_authenticate(wpa_s, bss, ssid); return; } if (wpa_s->connect_work) { wpa_dbg(wpa_s, MSG_DEBUG, "Reject wpa_supplicant_associate() call since connect_work exist"); return; } if (radio_work_pending(wpa_s, "connect")) { wpa_dbg(wpa_s, MSG_DEBUG, "Reject wpa_supplicant_associate() call since pending work exist"); return; } #ifdef CONFIG_SME if (ssid->mode == WPAS_MODE_IBSS || ssid->mode == WPAS_MODE_MESH) { /* Clear possibly set auth_alg, if any, from last attempt. */ wpa_s->sme.auth_alg = WPA_AUTH_ALG_OPEN; } #endif /* CONFIG_SME */ wpas_abort_ongoing_scan(wpa_s); cwork = os_zalloc(sizeof(*cwork)); if (cwork == NULL) return; cwork->bss = bss; cwork->ssid = ssid; // 开启工作队列 if (radio_add_work(wpa_s, bss ? bss->freq : 0, "connect", 1, wpas_start_assoc_cb, cwork) < 0) { os_free(cwork); } } ``` 接下来分析wpas_start_assoc_cb函数 ```c static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) { ret = wpa_drv_associate(wpa_s, ¶ms); } /* 1. struct wpa_driver_associate_params params; 2. wpa_supplicant_set_state(wpa_s, WPA_ASSOCIATING); 3. 填充 params 4. ret = wpa_drv_associate(wpa_s, ¶ms); 5. wpa_supplicant_req_auth_timeout(wpa_s, timeout, 0); 6. wpa_supplicant_initiate_eapol(wpa_s); */ //5.6步目前还不确定走不走,这个函数 囊括了 assoc、auth、EAPOL动作 ``` 接下来分析`wpa_drv_associate`函数 ###### wpa_drv_associate ```c static inline int wpa_drv_associate(struct wpa_supplicant *wpa_s, struct wpa_driver_associate_params *params) { if (wpa_s->driver->associate) { // 实际就是调用wpa_driver_nl80211_ops 中的 wpa_driver_nl80211_associate return wpa_s->driver->associate(wpa_s->drv_priv, params); } return -1; } ``` 接下来分析wpa_driver_nl80211_ops 1. wpa_driver_nl80211_associate ```c static int wpa_driver_nl80211_associate( void *priv, struct wpa_driver_associate_params *params) { struct i802_bss *bss = priv; struct wpa_driver_nl80211_data *drv = bss->drv; int ret = -1; struct nl_msg *msg; nl80211_unmask_11b_rates(bss); if (params->mode == IEEE80211_MODE_AP) return wpa_driver_nl80211_ap(drv, params); if (params->mode == IEEE80211_MODE_IBSS) return wpa_driver_nl80211_ibss(drv, params); if (!(drv->capa.flags & WPA_DRIVER_FLAGS_SME)) { enum nl80211_iftype nlmode = params->p2p ? NL80211_IFTYPE_P2P_CLIENT : NL80211_IFTYPE_STATION; if (wpa_driver_nl80211_set_mode(priv, nlmode) < 0) return -1; if (params->key_mgmt_suite == WPA_KEY_MGMT_SAE || params->key_mgmt_suite == WPA_KEY_MGMT_FT_SAE) bss->use_nl_connect = 1; else bss->use_nl_connect = 0; // 连接 return wpa_driver_nl80211_connect(drv, params, get_connect_handle(bss)); } nl80211_mark_disconnected(drv); wpa_printf(MSG_DEBUG, "nl80211: Associate (ifindex=%d)", drv->ifindex); //先创建一个基础的 msg NL80211_CMD_ASSOCIATE msg = nl80211_drv_msg(drv, 0, NL80211_CMD_ASSOCIATE); if (!msg) return -1; //将 parmas 参数塞进 msg ret = nl80211_connect_common(drv, params, msg); if (ret) goto fail; if (params->mgmt_frame_protection == MGMT_FRAME_PROTECTION_REQUIRED && nla_put_u32(msg, NL80211_ATTR_USE_MFP, NL80211_MFP_REQUIRED)) goto fail; if (params->fils_kek) { wpa_printf(MSG_DEBUG, " * FILS KEK (len=%u)", (unsigned int) params->fils_kek_len); if (nla_put(msg, NL80211_ATTR_FILS_KEK, params->fils_kek_len, params->fils_kek)) goto fail; } if (params->fils_nonces) { wpa_hexdump(MSG_DEBUG, " * FILS nonces (for AAD)", params->fils_nonces, params->fils_nonces_len); if (nla_put(msg, NL80211_ATTR_FILS_NONCES, params->fils_nonces_len, params->fils_nonces)) goto fail; } //将 msg 发出 /* - 放入 NL80211_CMD_ASSOCIATE - 把参数通过nl80211_connect_common函数在它里面put各种nl消息 - 调用send_and_recv_msgs_owner发送出去msg - */ ret = send_and_recv_msgs_owner(drv, msg, get_connect_handle(drv->first_bss), 1, NULL, NULL, NULL, NULL); msg = NULL; if (ret) { wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: MLME command failed (assoc): ret=%d (%s)", ret, strerror(-ret)); nl80211_dump_scan(drv); } else { wpa_printf(MSG_DEBUG, "nl80211: Association request send successfully"); } fail: nlmsg_free(msg); return ret; } ``` 2. wpa_driver_nl80211_connect ```c static int wpa_driver_nl80211_connect( struct wpa_driver_nl80211_data *drv, struct wpa_driver_associate_params *params, struct nl_sock *nl_connect) { int ret; // 尝试连接 下面分析该函数 ret = wpa_driver_nl80211_try_connect(drv, params, nl_connect); if (ret == -EALREADY) { /* * cfg80211 does not currently accept new connections if * we are already connected. As a workaround, force * disconnection and try again. * 如果已经连接上,就断开,从新连接 */ if (wpa_driver_nl80211_disconnect( drv, WLAN_REASON_PREV_AUTH_NOT_VALID, nl_connect)) return -1; ret = wpa_driver_nl80211_try_connect(drv, params, nl_connect); } return ret; } ``` 3. wpa_driver_nl80211_try_connect ```c static int wpa_driver_nl80211_try_connect( struct wpa_driver_nl80211_data *drv, struct wpa_driver_associate_params *params, struct nl_sock *nl_connect) { struct nl_msg *msg; enum nl80211_auth_type type; int ret; int algs; #ifdef CONFIG_DRIVER_NL80211_QCA if (params->req_key_mgmt_offload && params->psk && (params->key_mgmt_suite == WPA_KEY_MGMT_PSK || params->key_mgmt_suite == WPA_KEY_MGMT_PSK_SHA256 || params->key_mgmt_suite == WPA_KEY_MGMT_FT_PSK)) { wpa_printf(MSG_DEBUG, "nl80211: Key management set PSK"); ret = issue_key_mgmt_set_key(drv, params->psk, 32); if (ret) return ret; } #endif /* CONFIG_DRIVER_NL80211_QCA */ wpa_printf(MSG_DEBUG, "nl80211: Connect (ifindex=%d)", drv->ifindex); //第一步:创建消息nl消息 命令问为 NL80211_CMD_CONNECT msg = nl80211_drv_msg(drv, 0, NL80211_CMD_CONNECT); if (!msg) return -1; //第二步:传入参数放入nl attr中 ret = nl80211_connect_common(drv, params, msg); //这中间删除掉一部分,具体看代码 ret = nl80211_set_conn_keys(params, msg); // 发送消息 ret = send_and_recv_msgs_owner(drv, msg, nl_connect, 1, NULL,(void *) -1, NULL, NULL); msg = NULL; fail: nl80211_nlmsg_clear(msg); nlmsg_free(msg); return ret; } ``` 至此应用层完成工作,当驱动收到 NL80211_CMD_CONNECT 该消息后,会调用驱动的`nl80211_connect`函数进行处理 `qca/src/linux-4.4/net/wireless/nl80211.c`文件下nl80211_ops里面定义的nl80211_connect函数, ```c static const struct genl_ops nl80211_ops[] = { { .cmd = NL80211_CMD_CONNECT, .doit = nl80211_connect, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, } } ``` 下面就是驱动要干的事情了 4. 驱动层我们这里不做分析,当驱动处理后,同样会发送NL80211_CMD_CONNECT到应用层wpa,wpa ``` process_global_event do_process_drv_event { case NL80211_CMD_CONNECT: case NL80211_CMD_ROAM: mlme_event_connect(drv, cmd,xx) { 处理驱动上传的事件 } } ```